package com.amumu.drama.common.commpont;

import com.amumu.drama.common.base.annonation.OnContextInited;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

/**
 * 用于全局管理Spring的ApplicationContext
 *
 * @author BlackCat
 * @since 2012-7-17
 */
@Service
public class SpringContext implements ApplicationContextAware {

    private static ApplicationContext CONTEXT;

    private SpringContext() {
    }

    /** 用于Spring是否已经初始化完成 */
    //static boolean contextInited = false;
    private static void _checkContextInited() {
        if (CONTEXT == null) {
            throw new RuntimeException(
                    "Spring容器尚未初始化完成，请不要使用" + SpringContext.class.getSimpleName() + ".getBeanXXX方法" + "否则会导致异常，请不要在类的构造方法或字段初始化定义中调用这些方法，如果是在标记 @OnBeanInited.class 的方法中遇到本异常，请改用@" + OnContextInited.class.getSimpleName());
        }
    }

    public static boolean isContextInited() {
        return CONTEXT != null;
    }

    public static ApplicationContext getApplicationContext() {
        return CONTEXT;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        CONTEXT = applicationContext;
    }

    /**
     * 获取满足指定类型的bean的列表
     */
    public static <T> List<T> getBeansOfTypeAsList(Class<T> type) throws BeansException {
        _checkContextInited();
        return new ArrayList<T>(CONTEXT.getBeansOfType(type).values());
    }

    // 以下是从 ApplicationContext中代理出来的方法，加了初始化完成的判断，减少代码错误

    /**
     * 获取满足指定类型的bean，如果有多个会抛出异常，如果没有会返回null
     */
    public static <T> T getBeanOfType(Class<T> type) throws BeansException {
        _checkContextInited();
        Map<String, T> map = getBeansOfType(type);
        if (map.size() > 0) { // 如果有实现类，就注入，否则就留null
            if (map.size() > 1) {
                throw new IllegalStateException(type.getSimpleName() + "的实例只能有1个，现在有多个实例");
            }
            T v = map.values().iterator().next();
            return v;
        }
        return null;
    }

    /**
     * Return the number of beans defined in the factory.
     * <p>
     * Does not consider any hierarchy this factory may participate in, and ignores any singleton beans that have been registered by other means than bean definitions.
     *
     * @return the number of beans defined in the factory
     */
    public static int getBeanDefinitionCount() {
        _checkContextInited();
        return CONTEXT.getBeanDefinitionCount();
    }

    /**
     * Return the names of all beans defined in this factory.
     * <p>
     * Does not consider any hierarchy this factory may participate in, and ignores any singleton beans that have been registered by other means than bean definitions.
     *
     * @return the names of all beans defined in this factory, or an empty array if none defined
     */
    public static String[] getBeanDefinitionNames() {
        _checkContextInited();
        return CONTEXT.getBeanDefinitionNames();
    }

    /**
     * Return the names of beans matching the given type (including subclasses), judging from either bean definitions or the value of {@code getObjectType} in the case of FactoryBeans.
     * <p>
     * <b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i> check nested beans which might match the specified type as well.
     * <p>
     * Does consider objects created by FactoryBeans, which means that FactoryBeans will get initialized. If the object created by the FactoryBean doesn't match, the raw FactoryBean itself will be
     * matched against the type.
     * <p>
     * Does not consider any hierarchy this factory may participate in. Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors} to include beans in ancestor factories too.
     * <p>
     * Note: Does <i>not</i> ignore singleton beans that have been registered by other means than bean definitions.
     * <p>
     * This version of {@code getBeanNamesForType} matches all kinds of beans, be it singletons, prototypes, or FactoryBeans. In most implementations, the result will be the same as for
     * {@code getBeanNamesForType(type, true, true)}.
     * <p>
     * Bean names returned by this method should always return bean names <i>in the order of definition</i> in the backend configuration, as far as possible.
     *
     * @param type the class or interface to match, or {@code null} for all bean names
     * @return the names of beans (or objects created by FactoryBeans) matching the given object type (including subclasses), or an empty array if none
     * @see FactoryBean#getObjectType
     * @see BeanFactoryUtils#beanNamesForTypeIncludingAncestors(ListableBeanFactory, Class)
     */
    public static String[] getBeanNamesForType(Class<?> type) {
        _checkContextInited();
        return CONTEXT.getBeanNamesForType(type);
    }

    /**
     * Return the names of beans matching the given type (including subclasses), judging from either bean definitions or the value of {@code getObjectType} in the case of FactoryBeans.
     * <p>
     * <b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i> check nested beans which might match the specified type as well.
     * <p>
     * Does consider objects created by FactoryBeans if the "allowEagerInit" flag is set, which means that FactoryBeans will get initialized. If the object created by the FactoryBean doesn't match,
     * the raw FactoryBean itself will be matched against the type. If "allowEagerInit" is not set, only raw FactoryBeans will be checked (which doesn't require initialization of each FactoryBean).
     * <p>
     * Does not consider any hierarchy this factory may participate in. Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors} to include beans in ancestor factories too.
     * <p>
     * Note: Does <i>not</i> ignore singleton beans that have been registered by other means than bean definitions.
     * <p>
     * Bean names returned by this method should always return bean names <i>in the order of definition</i> in the backend configuration, as far as possible.
     *
     * @param type                 the class or interface to match, or {@code null} for all bean names
     * @param includeNonSingletons whether to include prototype or scoped beans too or just singletons (also applies to FactoryBeans)
     * @param allowEagerInit       whether to initialize <i>lazy-init singletons</i> and <i>objects created by FactoryBeans</i> (or by factory methods with a "factory-bean" reference) for the type check.
     *                             Note that FactoryBeans need to be eagerly initialized to determine their type: So be aware that passing in "true" for this flag will initialize FactoryBeans and "factory-bean"
     *                             references.
     * @return the names of beans (or objects created by FactoryBeans) matching the given object type (including subclasses), or an empty array if none
     * @see FactoryBean#getObjectType
     * @see BeanFactoryUtils#beanNamesForTypeIncludingAncestors(ListableBeanFactory, Class, boolean, boolean)
     */
    public static String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
        _checkContextInited();
        return CONTEXT.getBeanNamesForType(type, includeNonSingletons, allowEagerInit);
    }

    /**
     * Return an instance, which may be shared or independent, of the specified bean.
     * <p>
     * This method allows a Spring BeanFactory to be used as a replacement for the Singleton or Prototype design pattern. Callers may retain references to returned objects in the case of Singleton
     * beans.
     * <p>
     * Translates aliases back to the corresponding canonical bean name. Will ask the parent factory if the bean cannot be found in this factory instance.
     *
     * @param name the name of the bean to retrieve
     * @return an instance of the bean
     * @throws NoSuchBeanDefinitionException if there is no bean definition with the specified name
     * @throws BeansException                if the bean could not be obtained
     */
    public static Object getBean(String name) throws BeansException {
        _checkContextInited();
        return CONTEXT.getBean(name);
    }

    /**
     * Return an instance, which may be shared or independent, of the specified bean.
     * <p>
     * Behaves the same as {@link #getBean(String)}, but provides a measure of type safety by throwing a BeanNotOfRequiredTypeException if the bean is not of the required type. This means that
     * ClassCastException can't be thrown on casting the result correctly, as can happen with {@link #getBean(String)}.
     * <p>
     * Translates aliases back to the corresponding canonical bean name. Will ask the parent factory if the bean cannot be found in this factory instance.
     *
     * @param name         the name of the bean to retrieve
     * @param requiredType type the bean must match. Can be an interface or superclass of the actual class, or {@code null} for any match. For example, if the value is {@code Object.class}, this
     *                     method will succeed whatever the class of the returned instance.
     * @return an instance of the bean
     * @throws NoSuchBeanDefinitionException  if there is no such bean definition
     * @throws BeanNotOfRequiredTypeException if the bean is not of the required type
     * @throws BeansException                 if the bean could not be created
     */
    public static <T> T getBean(String name, Class<T> requiredType) throws BeansException {
        _checkContextInited();
        return CONTEXT.getBean(name, requiredType);
    }

    /**
     * Return the bean instances that match the given object type (including subclasses), judging from either bean definitions or the value of {@code getObjectType} in the case of FactoryBeans.
     * <p>
     * <b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i> check nested beans which might match the specified type as well.
     * <p>
     * Does consider objects created by FactoryBeans, which means that FactoryBeans will get initialized. If the object created by the FactoryBean doesn't match, the raw FactoryBean itself will be
     * matched against the type.
     * <p>
     * Does not consider any hierarchy this factory may participate in. Use BeanFactoryUtils' {@code beansOfTypeIncludingAncestors} to include beans in ancestor factories too.
     * <p>
     * Note: Does <i>not</i> ignore singleton beans that have been registered by other means than bean definitions.
     * <p>
     * This version of getBeansOfType matches all kinds of beans, be it singletons, prototypes, or FactoryBeans. In most implementations, the result will be the same as for
     * {@code getBeansOfType(type, true, true)}.
     * <p>
     * The Map returned by this method should always return bean names and corresponding bean instances <i>in the order of definition</i> in the backend configuration, as far as possible.
     *
     * @param type the class or interface to match, or {@code null} for all concrete beans
     * @return a Map with the matching beans, containing the bean names as keys and the corresponding bean instances as values
     * @throws BeansException if a bean could not be created
     * @see FactoryBean#getObjectType
     * @see BeanFactoryUtils#beansOfTypeIncludingAncestors(ListableBeanFactory, Class)
     * @since 1.1.2
     */
    public static <T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException {
        _checkContextInited();
        return CONTEXT.getBeansOfType(type);
    }

    /**
     * Return the bean instance that uniquely matches the given object type, if any.
     *
     * @param requiredType type the bean must match; can be an interface or superclass. {@code null} is disallowed.
     *                     <p>
     *                     This method goes into {@link ListableBeanFactory} by-type lookup territory but may also be translated into a conventional by-name lookup based on the name of the given type. For more
     *                     extensive retrieval operations across sets of beans, use {@link ListableBeanFactory} and/or {@link BeanFactoryUtils}.
     * @return an instance of the single bean matching the required type
     * @throws NoSuchBeanDefinitionException   if no bean of the given type was found
     * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found
     * @see ListableBeanFactory
     * @since 3.0
     */
    public static <T> T getBean(Class<T> requiredType) throws BeansException {
        _checkContextInited();
        return CONTEXT.getBean(requiredType);
    }

    /**
     * Return an instance, which may be shared or independent, of the specified bean.
     * <p>
     * Allows for specifying explicit constructor arguments / factory method arguments, overriding the specified default arguments (if any) in the bean definition.
     *
     * @param name the name of the bean to retrieve
     * @param args arguments to use when creating a bean instance using explicit arguments (only applied when creating a new instance as opposed to retrieving an existing one)
     * @return an instance of the bean
     * @throws NoSuchBeanDefinitionException if there is no such bean definition
     * @throws BeanDefinitionStoreException  if arguments have been given but the affected bean isn't a prototype
     * @throws BeansException                if the bean could not be created
     * @since 2.5
     */
    public static Object getBean(String name, Object... args) throws BeansException {
        _checkContextInited();
        return CONTEXT.getBean(name, args);
    }

    /**
     * Return the bean instances that match the given object type (including subclasses), judging from either bean definitions or the value of {@code getObjectType} in the case of FactoryBeans.
     * <p>
     * <b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i> check nested beans which might match the specified type as well.
     * <p>
     * Does consider objects created by FactoryBeans if the "allowEagerInit" flag is set, which means that FactoryBeans will get initialized. If the object created by the FactoryBean doesn't match,
     * the raw FactoryBean itself will be matched against the type. If "allowEagerInit" is not set, only raw FactoryBeans will be checked (which doesn't require initialization of each FactoryBean).
     * <p>
     * Does not consider any hierarchy this factory may participate in. Use BeanFactoryUtils' {@code beansOfTypeIncludingAncestors} to include beans in ancestor factories too.
     * <p>
     * Note: Does <i>not</i> ignore singleton beans that have been registered by other means than bean definitions.
     * <p>
     * The Map returned by this method should always return bean names and corresponding bean instances <i>in the order of definition</i> in the backend configuration, as far as possible.
     *
     * @param type                 the class or interface to match, or {@code null} for all concrete beans
     * @param includeNonSingletons whether to include prototype or scoped beans too or just singletons (also applies to FactoryBeans)
     * @param allowEagerInit       whether to initialize <i>lazy-init singletons</i> and <i>objects created by FactoryBeans</i> (or by factory methods with a "factory-bean" reference) for the type check.
     *                             Note that FactoryBeans need to be eagerly initialized to determine their type: So be aware that passing in "true" for this flag will initialize FactoryBeans and "factory-bean"
     *                             references.
     * @return a Map with the matching beans, containing the bean names as keys and the corresponding bean instances as values
     * @throws BeansException if a bean could not be created
     * @see FactoryBean#getObjectType
     * @see BeanFactoryUtils#beansOfTypeIncludingAncestors(ListableBeanFactory, Class, boolean, boolean)
     */
    public static <T> Map<String, T> getBeansOfType(Class<T> type, boolean includeNonSingletons, boolean allowEagerInit) throws BeansException {
        _checkContextInited();
        return CONTEXT.getBeansOfType(type, includeNonSingletons, allowEagerInit);
    }

    /**
     * Return an instance, which may be shared or independent, of the specified bean.
     * <p>
     * Allows for specifying explicit constructor arguments / factory method arguments, overriding the specified default arguments (if any) in the bean definition.
     *
     * @param requiredType type the bean must match; can be an interface or superclass. {@code null} is disallowed.
     *                     <p>
     *                     This method goes into {@link ListableBeanFactory} by-type lookup territory but may also be translated into a conventional by-name lookup based on the name of the given type. For more
     *                     extensive retrieval operations across sets of beans, use {@link ListableBeanFactory} and/or {@link BeanFactoryUtils}.
     * @param args         arguments to use when creating a bean instance using explicit arguments (only applied when creating a new instance as opposed to retrieving an existing one)
     * @return an instance of the bean
     * @throws NoSuchBeanDefinitionException if there is no such bean definition
     * @throws BeanDefinitionStoreException  if arguments have been given but the affected bean isn't a prototype
     * @throws BeansException                if the bean could not be created
     * @since 4.1
     */
    public static <T> T getBean(Class<T> requiredType, Object... args) throws BeansException {
        _checkContextInited();
        return CONTEXT.getBean(requiredType, args);
    }

    /**
     * Find all names of beans whose {@code Class} has the supplied {@link Annotation} type, without creating any bean instances yet.
     *
     * @param annotationType the type of annotation to look for
     * @return the names of all matching beans
     * @since 4.0
     */
    public static String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType) {
        _checkContextInited();
        return CONTEXT.getBeanNamesForAnnotation(annotationType);
    }

    /**
     * Find all beans whose {@code Class} has the supplied {@link Annotation} type, returning a Map of bean names with corresponding bean instances.
     *
     * @param annotationType the type of annotation to look for
     * @return a Map with the matching beans, containing the bean names as keys and the corresponding bean instances as values
     * @throws BeansException if a bean could not be created
     * @since 3.0
     */
    public static Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) throws BeansException {
        _checkContextInited();
        return CONTEXT.getBeansWithAnnotation(annotationType);
    }
    /**
     * 用于内部bean排序，按类全名的字典序，但org.etnaframework.框架的类优先
     */
    public static class ByNameFrameworkPriorComparator implements Comparator<Object> {

        public static final ByNameFrameworkPriorComparator INSTANCE = new ByNameFrameworkPriorComparator();

        @Override
        public int compare(Object o1, Object o2) {
            String n1 = o1.getClass().getName();
            String n2 = o2.getClass().getName();
            if (n1.startsWith("org.etnaframework.")) {
                n1 = " " + n1;
            }
            if (n2.startsWith("org.etnaframework.")) {
                n2 = " " + n2;
            }
            return n1.compareTo(n2);
        }
    }
}
